package game.ai.aoi;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AoiAlgorithm {

	private static AoiAlgorithm instance;

	public static AoiAlgorithm getInstance() {
		if (instance == null) {
			instance = new AoiAlgorithm();
		}
		return instance;
	}

	/**
	 * 状态发生改变时调用，移动，删除等
	 * 
	 * @param space
	 * @param objId
	 * @param posPair
	 * @param modeStr
	 */
	public void onAction(AoiSpace space, int objId, short[] posPair, byte[] modeStr) {
		Map<Integer, AoiObject> objMap = space.objMap;
		AoiObject aoiObject = objMap.get(objId);
		if (aoiObject == null) {
			aoiObject = new AoiObject(objId);
			objMap.put(objId, aoiObject);
		}
		boolean setWatcher = false;
		boolean setMarker = false;
		for (int i = 0; i < modeStr.length; i++) {
			byte modeFlag = modeStr[i];
			switch (modeFlag) {
			case AoiConstants.MODE_MARKER:
				setMarker = true;
				break;
			case AoiConstants.MODE_WATCHER:
				setWatcher = true;
				break;
			case AoiConstants.MODE_DROP:
				if ((aoiObject.mode & AoiConstants.MODE_DROP) == 0) {
					aoiObject.mode = AoiConstants.MODE_DROP;
					objMap.remove(aoiObject);
				}
				return;
			default:
				break;
			}
		}
		if ((aoiObject.mode & AoiConstants.MODE_DROP) != 0) {
			aoiObject.mode &= ~AoiConstants.MODE_DROP;
		}
		boolean isChanged = changeMode(aoiObject, setWatcher, setMarker);
		copyPosition(posPair, aoiObject.posPair);
		if (isChanged || !isNear(posPair, aoiObject.lastPosition)) {
			copyPosition(posPair, aoiObject.lastPosition);
			aoiObject.mode |= AoiConstants.MODE_MOVE;
			aoiObject.version++;
		}
	}

	private void copyPosition(short[] srcPosPair, short[] targetPosPair) {
		targetPosPair[0] = srcPosPair[0];
		targetPosPair[1] = srcPosPair[1];
	}

	private boolean isNear(short[] posPair, short[] lastPosition) {
		short squareDistance = squareDistance(posPair, lastPosition);
		return squareDistance <= (AoiConstants.HalfEyeShotWidth + AoiConstants.HalfEyeShotHeight) / 2 ? true : false;
	}

	private short squareDistance(short[] posPair1, short[] posPair2) {
		return (short) (Math.abs(posPair1[0] - posPair2[0]) + Math.abs(posPair1[1] - posPair2[1]));
	}

	private boolean changeMode(AoiObject aoiObject, boolean setWatcher, boolean setMarker) {
		boolean changed = false;
		if (aoiObject.mode == 0) {
			if (setWatcher) {
				aoiObject.mode = AoiConstants.MODE_WATCHER;
			}
			if (setMarker) {
				aoiObject.mode |= AoiConstants.MODE_MARKER;
			}
			return true;
		}
		if (setWatcher) {
			if ((aoiObject.mode & AoiConstants.MODE_WATCHER) == 0) {
				aoiObject.mode |= AoiConstants.MODE_WATCHER;
				changed = true;
			}
		} else {
			if ((aoiObject.mode & AoiConstants.MODE_WATCHER) != 0) {
				aoiObject.mode &= ~AoiConstants.MODE_WATCHER;
				changed = true;
			}
		}
		if (setMarker) {
			if ((aoiObject.mode & AoiConstants.MODE_MARKER) == 0) {
				aoiObject.mode |= AoiConstants.MODE_MARKER;
				changed = true;
			}
		} else {
			if ((aoiObject.mode & AoiConstants.MODE_MARKER) != 0) {
				aoiObject.mode &= ~AoiConstants.MODE_MARKER;
				changed = true;
			}
		}
		return changed;
	}

	/**
	 * 定时执行更新操作
	 */
	public void update(AoiSpace space) {
		flushHotPair(space);
		initSpaceStatus(space);
		genHotPair(space, space.watcherStatic, space.markerMove);
		genHotPair(space, space.watcherMove, space.markerStatic);
		genHotPair(space, space.watcherMove, space.markerMove);
	}

	private void genHotPair(AoiSpace space, Set<AoiObject> watcherSet, Set<AoiObject> markerSet) {
		for (AoiObject watcher : watcherSet) {
			for (AoiObject marker : markerSet) {
				if (watcher == marker) {
					continue;
				}
				short squareDistance = squareDistance(watcher.posPair, marker.posPair);
				if (squareDistance < AoiConstants.HalfEyeShotWidth + AoiConstants.HalfEyeShotHeight) {
					space.aoiEnterMsg(watcher.id, marker.id);
					continue;
				} else if (squareDistance > AoiConstants.EyeShotWidth + AoiConstants.EyeShotHeight) {
					space.aoiLeaveMsg(watcher.id, marker.id);
					continue;
				}
				AoiHotPair hotPair = new AoiHotPair();
				hotPair.watcher = watcher;
				hotPair.marker = marker;
				hotPair.watcherVersion = watcher.version;
				hotPair.markerVersion = marker.version;
				space.hotPairList.add(hotPair);
			}
		}
	}

	private void initSpaceStatus(AoiSpace space) {
		space.watcherStatic.clear();
		space.watcherMove.clear();
		space.markerStatic.clear();
		space.markerMove.clear();
		for (AoiObject aoiObject : space.objMap.values()) {
			if ((aoiObject.mode & AoiConstants.MODE_WATCHER) > 0) {
				if ((aoiObject.mode & AoiConstants.MODE_MOVE) > 0) {
					space.watcherMove.add(aoiObject);
					aoiObject.mode &= ~AoiConstants.MODE_MOVE;
				} else {
					space.watcherStatic.add(aoiObject);
				}
			}
			if ((aoiObject.mode & AoiConstants.MODE_MARKER) > 0) {
				if ((aoiObject.mode & AoiConstants.MODE_MOVE) > 0) {
					space.markerMove.add(aoiObject);
					aoiObject.mode &= ~AoiConstants.MODE_MOVE;
				} else {
					space.markerStatic.add(aoiObject);
				}
			}
		}
	}

	private void flushHotPair(AoiSpace space) {
		List<AoiHotPair> hotPairList = space.hotPairList;
		Iterator<AoiHotPair> hotPairIte = hotPairList.iterator();
		while (hotPairIte.hasNext()) {
			AoiHotPair hotPair = hotPairIte.next();
			if (hotPair.watcher.version != hotPair.watcherVersion || hotPair.marker.version != hotPair.markerVersion || (hotPair.watcher.mode & AoiConstants.MODE_DROP) > 0
					|| (hotPair.marker.mode & AoiConstants.MODE_DROP) > 0) {
			} else {
				short squareDistance = squareDistance(hotPair.watcher.posPair, hotPair.marker.posPair);
				if (squareDistance < AoiConstants.HalfEyeShotWidth + AoiConstants.HalfEyeShotHeight) {
					space.aoiEnterMsg(hotPair.watcher.id, hotPair.marker.id);
				} else if (squareDistance > AoiConstants.EyeShotWidth + AoiConstants.EyeShotHeight) {
					space.aoiLeaveMsg(hotPair.watcher.id, hotPair.marker.id);
				}
			}
			hotPairIte.remove();
		}
	}

}
